﻿using System;
using System.Linq;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Threading;
using System.Configuration;
using CloudBackupLib;

namespace CloudBackup
{
    ///////////////////////////////////////////////////////////////////////////
    //
    // Name
    //  MainWindow
    //
    ///////////////////////////////////////////////////////////////////////////
	public partial class MainWindow : Window
	{
        private enum Operation
        {
            None,
            Backup,
            Restore
        }
        private Operation _CurrentOperation = Operation.None;
        private VirtualMachine _CurrentOperationVirtualMachine = null;

        private enum BackupStep
        {
            Delete,
            Suspend,
            Export,
            Resume,
            Compress,
            Upload,
            Done
        };
        private BackupStep _CurrentBackupStep = BackupStep.Done;

        private enum RestoreStep
        {
            Suspend,
            Delete,
            Download,
            Uncompress,
            Import,
            Resume,
            Done
        };
        private RestoreStep _CurrentRestoreStep = RestoreStep.Done;
        private bool _OperationCancelled = false;
        private ApplicationException _OperationException;

        private Timer _RefreshTimer;
        private int _RefreshFrequency = 10000;

        private string _ExportLocation = ConfigurationManager.AppSettings["ExportLocation"];
        private string _CompressLocation = ConfigurationManager.AppSettings["CompressLocation"];
        private string _DownloadLocation = ConfigurationManager.AppSettings["DownloadLocation"];
        private string _UncompressLocation = ConfigurationManager.AppSettings["UncompressLocation"];

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  MainWindow Constructor
        //
        ///////////////////////////////////////////////////////////////////////
		public MainWindow()
		{
			this.InitializeComponent();

            _RefreshFrequency = Int32.Parse(ConfigurationManager.AppSettings["LocalVirtualMachineRefreshRateInMilliseconds"]);
            _RefreshTimer = new Timer(OnRefreshTimer, null, _RefreshFrequency, Timeout.Infinite); // Refresh every 10 seconds

            Closed += new EventHandler(OnClosed);
		}

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnFocus
        //
        // Parameters
        //  sender : The sender of the message
        //  e : The event args
        //
        ///////////////////////////////////////////////////////////////////////
        void OnClosed(object sender, EventArgs e)
        {
            // Clean up the intermediate paths
            //
            if(bool.Parse(ConfigurationManager.AppSettings["CleanIntermediatePaths"]))
            {
                Directory.Delete(_ExportLocation, true);
                Directory.CreateDirectory(_ExportLocation);
                Directory.Delete(_CompressLocation, true);
                Directory.CreateDirectory(_CompressLocation);
                Directory.Delete(_DownloadLocation, true);
                Directory.CreateDirectory(_DownloadLocation);
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnListBoxSelectionChanged
        //
        // Parameters
        //  sender : The sender of the event
        //  e : The event args
        //
        // Remarks
        //  Enforce only having one item selected in either the local or
        //  remote virtual machine list boxes
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnListBoxSelectionChanged(object sender, RoutedEventArgs e)
        {
            ListBox listBox = sender as ListBox;

            if(listBox.SelectedItem != null)
            {
                if(listBox == localVirtualMachineListBox)
                {
                    remoteVirtualMachineListBox.SelectedItem = null;
                }
                else if(listBox == remoteVirtualMachineListBox)
                {
                    localVirtualMachineListBox.SelectedItem = null;
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnBackupClick
        //
        // Parameters
        //  sender : The control that is being clicked
        //  e : The event arguments
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnBackupClick(object sender, RoutedEventArgs e)
        {
            VirtualMachine virtualMachine = (VirtualMachine)localVirtualMachineListBox.SelectedItem;

            // No virtual machine selected
            //
            if(virtualMachine == null)
            {
                MessageBox.Show("Please select a local virtual machine for backup.", "Backup", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            VirtualMachine virtualMachineToDelete = HandleDevStorageBug(virtualMachine);
            if(virtualMachineToDelete != null)
            {
                if(MessageBox.Show("A bug in the devstorage version of Azure fails uploads for existing blobs.  The backup must be deleted first.  Would you like to do so now?", "Backup", MessageBoxButton.YesNo, MessageBoxImage.Error) == MessageBoxResult.Yes)
                {
                    virtualMachineToDelete.Delete(null);
                    VirtualMachineProvider.GetRemoteVirtualMachines(false);
                }

                return;
            }

            VirtualMachine.State currentState = virtualMachine.CurrentState; // Will query the current state from WMI

            // The VM must be in the stopped or saved state to continue
            //
            if(currentState != VirtualMachine.State.Saved && currentState != VirtualMachine.State.Stopped)
            {
                // If the state is running, we can suspend it before continuing
                //
                if(currentState == VirtualMachine.State.Running)
                {
                    MessageBoxResult result = MessageBox.Show("The selected virtual machine is currently running.  Would you like to suspend the virtual machine for backup?", "Backup", MessageBoxButton.OKCancel, MessageBoxImage.Question);

                    // If the user decides not to suspend the VM
                    //
                    if(result == MessageBoxResult.Cancel)
                    {
                        return;
                    }
                }
                else // Unable to continue
                {
                    MessageBox.Show("The selected virtual machine in an invalid state for backup.  It must be either stopped or saved.  The current state is: " + currentState, "Backup", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }

            // Clear the progress screen and make it visible
            //
            UpdateProgress(null);
            _OperationCancelled = false;
            _CurrentOperation = Operation.Backup;
            ProgressPanel.Visibility = System.Windows.Visibility.Visible;

            // Start the backup process async
            //
            _CurrentOperationVirtualMachine = virtualMachine;
            Action action = delegate()
            {
                Backup(virtualMachine);
            };

            action.BeginInvoke(OnOperationComplete, action);
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnRestoreClick
        //
        // Parameters
        //  sender : The control that is being clicked
        //  e : The event arguments
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnRestoreClick(object sender, RoutedEventArgs e)
        {
            VirtualMachine virtualMachine = (VirtualMachine)remoteVirtualMachineListBox.SelectedItem;

            // No virtual machine selected
            //
            if(virtualMachine == null)
            {
                MessageBox.Show("Please select a remote virtual machine for restoration.", "Restore", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            // Check to see if there is a local VM with the same name that needs to be deleted
            //
            if(VirtualMachineProvider.GetLocalVirtualMachines(true).Where(vm => vm.Name == virtualMachine.Name).FirstOrDefault() != null)
            {
                MessageBoxResult result = MessageBox.Show("A local virtual machine with the same name already exists and must be deleted before restoring from backup.  Would you like to delete it now?", "Restore", MessageBoxButton.OKCancel, MessageBoxImage.Question);

                if(result == MessageBoxResult.Cancel)
                {
                    return;
                }
            }

            // Ask the user if the want to resume the VM on restore
            //
            bool resumeAfterImport = MessageBox.Show("Would you like to resume the virtual machine after restoring?", "Restore", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;

            // Clear the progress screen and make it visible
            //
            UpdateProgress(null);
            _OperationCancelled = false;
            _CurrentOperation = Operation.Restore;
            ProgressPanel.Visibility = System.Windows.Visibility.Visible;

            // Start the restore process async
            //
            _CurrentOperationVirtualMachine = virtualMachine;
            Action action = delegate()
            {
                Restore(virtualMachine, resumeAfterImport);
            };

            action.BeginInvoke(OnOperationComplete, action);
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnCancelClick
        //
        // Parameters
        //  sender : The control that is being clicked
        //  e : The event arguments
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnCancelClick(object sender, RoutedEventArgs e)
        {
            if(_CurrentOperationVirtualMachine != null)
            {
                _OperationCancelled = true;
                Operation currentOperation = _CurrentOperation;

                Action<VirtualMachine, bool> callback = delegate(VirtualMachine virtualMachine, bool success)
                {
                    Action action = delegate()
                    {
                        ProgressPanel.Visibility = System.Windows.Visibility.Hidden;
                        MessageBox.Show("The operation was cancelled.", currentOperation.ToString(), MessageBoxButton.OK, MessageBoxImage.Information);
                    };

                    if(Thread.CurrentThread != Dispatcher.Thread)
                    {
                        Dispatcher.Invoke(action);
                    }
                    else
                    {
                        action();
                    }
                };

                _CurrentOperationVirtualMachine.Cancel(callback);
            }
        }
       
        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Backup
        //
        // Parameters
        //  virtualMachine : The target remote virtual machine to backup
        //
        // Remarks
        //  The operation goes through a series of steps, each of which
        //  calls into the supplied virtual machine asynchronously and waits
        //  for completion.  If any of the steps fails, an application
        //  exception is created and stored in _OperationException
        //
        ///////////////////////////////////////////////////////////////////////
        private void Backup(VirtualMachine virtualMachine)
        {
            // Determine whether the virtual machine needs to be suspended
            // before being exported
            //
            bool suspendedDuringBackup = false;
            if(virtualMachine.CurrentState == VirtualMachine.State.Running)
            {
                suspendedDuringBackup = true;
                _CurrentBackupStep = BackupStep.Suspend;
            }
            else
            {
                _CurrentBackupStep = BackupStep.Export;
            }

            // Each step in the operation is executed asynchronously.
            // If one of them fails, it's result will end up here
            //
            bool asyncResult = true;

            // This method will spin until this variable becomes true
            // while waiting for a step method to complete
            //
            bool stepCompleted = false;

            // Async callback for the step methods
            //
            Action<VirtualMachine, bool> callback = delegate(VirtualMachine context, bool success)
            {
                asyncResult = success;
                stepCompleted = true;
            };

            while(_CurrentBackupStep != BackupStep.Done && _OperationCancelled == false)
            {
                stepCompleted = false;

                // Execute the next step in the restore process asynchronously
                //
                switch(_CurrentBackupStep)
                {
                    case BackupStep.Suspend:
                    {
                        virtualMachine.Suspend(callback);
                        break;
                    }
                    case BackupStep.Export:
                    {
                        virtualMachine.Export(_ExportLocation, callback);
                        break;
                    }
                    case BackupStep.Resume:
                    {
                        virtualMachine.Resume(callback);
                        break;
                    }
                    case BackupStep.Compress:
                    {
                        virtualMachine.Compress(Path.Combine(_ExportLocation, virtualMachine.Name), _CompressLocation, callback);
                        break;
                    }
                    case BackupStep.Upload:
                    {
                        virtualMachine.Upload(Path.Combine(_CompressLocation, virtualMachine.Name) + ".zip", callback);
                        break;
                    }
                }

                // Poll for completion
                //
                while(stepCompleted == false && _OperationCancelled == false)
                {
                    Dispatcher.Invoke(new Action<VirtualMachine>(UpdateProgress), virtualMachine);
                    Thread.Sleep(200);
                }

                // Check the result of the step
                //
                if(asyncResult)
                {
                    ++_CurrentBackupStep; // Proceed to the next step if successful

                    // No need to resume if we didn't previously suspend
                    //
                    if(_CurrentBackupStep == BackupStep.Resume && suspendedDuringBackup == false)
                    {
                        ++_CurrentBackupStep;
                    }
                }
                else if(_OperationCancelled == false) // Otherwise handle errors
                {
                    switch(_CurrentBackupStep)
                    {
                        case BackupStep.Suspend:
                        {
                            _OperationException = new ApplicationException("Suspend failed");
                            return;
                        }
                        case BackupStep.Export:
                        {
                            _OperationException = new ApplicationException("Export failed");
                            return;
                        }
                        case BackupStep.Resume:
                        {
                            _OperationException = new ApplicationException("Resume failed");
                            return;
                        }
                        case BackupStep.Compress:
                        {
                            _OperationException = new ApplicationException("Compress failed");
                            return;
                        }
                        case BackupStep.Upload:
                        {
                            _OperationException = new ApplicationException("Upload failed");
                            return;
                        }
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Restore
        //
        // Parameters
        //  virtualMachine : The target remote virtual machine to restore
        //  resumeAfterImport : If true, once the virtual machine is imported
        //      into HyperV, it will be resumed
        //
        // Remarks
        //  The operation goes through a series of steps, each of which
        //  calls into the supplied virtual machine asynchronously and waits
        //  for completion.  If any of the steps fails, an application
        //  exception is created and stored in _OperationException
        //
        ///////////////////////////////////////////////////////////////////////
        private void Restore(VirtualMachine virtualMachine, bool resumeAfterImport)
        {
            // Check to see if there is a local VM with the same name that needs to be deleted
            //
            VirtualMachine virtualMachineToDelete = VirtualMachineProvider.GetLocalVirtualMachines(true).Where(vm => vm.Name == virtualMachine.Name).FirstOrDefault();
            if(virtualMachineToDelete != null)
            {
                // Check to see if it needs to be suspended first
                //
                if(virtualMachineToDelete.CurrentState == VirtualMachine.State.Running)
                {
                    _CurrentRestoreStep = RestoreStep.Suspend;
                }
                else
                {
                    _CurrentRestoreStep = RestoreStep.Delete;
                }
            }
            else
            {
                _CurrentRestoreStep = RestoreStep.Download;
            }

            // Each step in the operation is executed asynchronously.
            // If one of them fails, it's result will end up here
            //
            bool asyncResult = true; 

            // This method will spin until this variable becomes true
            // while waiting for a step method to complete
            //
            bool stepCompleted = false;

            // Async callback for the step methods
            //
            Action<VirtualMachine, bool> callback = delegate(VirtualMachine context, bool success)
            {
                asyncResult = success;
                stepCompleted = true;
            };

            while(_CurrentRestoreStep != RestoreStep.Done && _OperationCancelled == false)
            {
                stepCompleted = false;

                // Execute the next step in the restore process asynchronously
                //
                switch(_CurrentRestoreStep)
                {
                    case RestoreStep.Suspend:
                    {
                        virtualMachineToDelete.Suspend(callback);
                        break;
                    }
                    case RestoreStep.Delete:
                    {
                        virtualMachineToDelete.Delete(callback);
                        break;
                    }
                    case RestoreStep.Download:
                    {
                        virtualMachine.Download(_DownloadLocation, callback);
                        break;
                    }
                    case RestoreStep.Uncompress:
                    {
                        virtualMachine.Uncompress(Path.Combine(_DownloadLocation, virtualMachine.Name) + ".zip", _UncompressLocation, callback);
                        break;
                    }
                    case RestoreStep.Import:
                    {
                        virtualMachine.Import(Path.Combine(_UncompressLocation, virtualMachine.Name), callback);
                        break;
                    }
                    case RestoreStep.Resume:
                    {
                        virtualMachine.Resume(callback);
                        break;
                    }
                }

                // Poll for completion
                //
                while(stepCompleted == false && _OperationCancelled == false)
                {
                    if(_CurrentRestoreStep == RestoreStep.Suspend || _CurrentRestoreStep == RestoreStep.Delete)
                    {
                        Dispatcher.Invoke(new Action<VirtualMachine>(UpdateProgress), virtualMachineToDelete);
                    }
                    else
                    {
                        Dispatcher.Invoke(new Action<VirtualMachine>(UpdateProgress), virtualMachine);
                    }
                    Thread.Sleep(200);
                }

                // Check the result of the step
                //
                if(asyncResult)
                {                    
                    ++_CurrentRestoreStep; // Proceed to the next step if successful

                    // Skip resuming if not requested
                    //
                    if(_CurrentRestoreStep == RestoreStep.Resume && resumeAfterImport == false)
                    {
                        ++_CurrentRestoreStep;
                    }
                }
                else if(_OperationCancelled == false) // Otherwise handle errors
                {
                    switch(_CurrentRestoreStep)
                    {
                        case RestoreStep.Suspend:
                        {
                            _OperationException = new ApplicationException("Suspend Failed");
                            return;
                        }
                        case RestoreStep.Delete:
                        {
                            _OperationException = new ApplicationException("Delete failed");
                            return;
                        }
                        case RestoreStep.Download:
                        {
                            _OperationException = new ApplicationException("Download failed");
                            return;
                        }
                        case RestoreStep.Uncompress:
                        {
                            _OperationException = new ApplicationException("Uncompress failed");
                            return;
                        }
                        case RestoreStep.Import:
                        {
                            _OperationException = new ApplicationException("Import failed");
                            return;
                        }
                        case RestoreStep.Resume:
                        {
                            _OperationException = new ApplicationException("Resume failed");
                            return;
                        }
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnOperationComplete
        //
        // Parameters
        //  result : Contains the result of the async operation
        //
        // Remarks
        //  The Backup and Restore methods get wrapped in a delegate that is
        //  executed asynchronously from OnBackupClick and OnRestoreClick.  
        //  This method is called when either of those methods completes
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnOperationComplete(IAsyncResult result)
        {
            // Clean up the async invocation object
            //
            ((Action)result.AsyncState).EndInvoke(result);

            Action action = delegate()
            {
                // Refresh both lists
                //
                VirtualMachineProvider.GetLocalVirtualMachines(false);
                VirtualMachineProvider.GetRemoteVirtualMachines(false);

                if(_OperationCancelled == false)
                {
                    ProgressPanel.Visibility = System.Windows.Visibility.Hidden;
                }
                
                if(_OperationException != null)
                {
                    MessageBox.Show("An error occurred during the operation.\n" + _OperationException.Message, _CurrentOperation.ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
                }
                else if(_OperationCancelled == false)
                {
                    MessageBox.Show("The operation completed successfully.", _CurrentOperation.ToString(), MessageBoxButton.OK, MessageBoxImage.Information);
                }

                _OperationException = null;
                _CurrentOperation = Operation.None;
                _CurrentOperationVirtualMachine = null;
            };

            if(Thread.CurrentThread != Dispatcher.Thread)
            {
                Dispatcher.Invoke(action);
            }
            else
            {
                action();
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  UpdateProgress
        //
        // Parameters
        //  virtualMachine : The virtual machine that is has an outstanding
        //      operation.  Can be null to clear the controls
        //
        // Remarks
        //  Called repeatedly during an operation to refresh the progress 
        //  controls
        //
        ///////////////////////////////////////////////////////////////////////
        private void UpdateProgress(VirtualMachine virtualMachine)
        {
            if(virtualMachine != null)
            {
                progressTitle.Text = _CurrentOperation.ToString() + ": " + virtualMachine.Name;

                int? percentComplete = virtualMachine.GetPercentComplete();

                if(percentComplete != null)
                {
                    progressBar.Value = (double)percentComplete;
                    progressPercentLabel.Content = percentComplete.ToString() + "%";
                }
            }

            switch(_CurrentOperation)
            {
                case Operation.Backup:
                {
                    switch(_CurrentBackupStep)
                    {
                        case BackupStep.Suspend:
                        {
                            progressDescription.Content = "Suspending...";
                            break;
                        }
                        case BackupStep.Export:
                        {
                            progressDescription.Content = "Exporting...";
                            break;
                        }
                        case BackupStep.Resume:
                        {
                            progressDescription.Content = "Resuming...";
                            break;
                        }
                        case BackupStep.Compress:
                        {
                            progressDescription.Content = "Compressing...";
                            break;
                        }
                        case BackupStep.Upload:
                        {
                            progressDescription.Content = "Uploading...";
                            break;
                        }
                    }

                    break;
                }
                case Operation.Restore:
                {
                    switch(_CurrentRestoreStep)
                    {
                        case RestoreStep.Suspend:
                        {
                            progressDescription.Content = "Suspending...";
                            break;
                        }
                        case RestoreStep.Delete:
                        {
                            progressDescription.Content = "Deleting...";
                            break;
                        }
                        case RestoreStep.Download:
                        {
                            progressDescription.Content = "Downloading...";
                            break;
                        }
                        case RestoreStep.Uncompress:
                        {
                            progressDescription.Content = "Uncompressing...";
                            break;
                        }
                        case RestoreStep.Import:
                        {
                            progressDescription.Content = "Importing...";
                            break;
                        }
                        case RestoreStep.Resume:
                        {
                            progressDescription.Content = "Resuming...";
                            break;
                        }
                    }

                    break;
                }
                case Operation.None:
                {
                    progressPercentLabel.Content = "0%";
                    progressBar.Value = 0;
                    progressDescription.Content = "";

                    break;
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnRefreshTimer
        //
        // Parameters
        //  context : A caller-supplied argument for the Timer
        //
        // Remarks
        //  This method is used to periodically get the latest list of
        //  local virtual machines, since their state may change or more may
        //  be added while the app is running
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnRefreshTimer(Object context)
        {
            Action action = delegate()
            {
                // Save the name of any selected VM
                //
                VirtualMachine virtualMachine = (VirtualMachine)localVirtualMachineListBox.SelectedItem;

                // Refresh the local VM list
                //
                VirtualMachineProvider.GetLocalVirtualMachines(false);

                // Try to restore the old selected item
                //
                if(virtualMachine != null)
                {
                    localVirtualMachineListBox.SelectedItem = VirtualMachineProvider.GetLocalVirtualMachines(true).Where(vm => vm.Name == virtualMachine.Name).FirstOrDefault();
                }

                _RefreshTimer.Change(_RefreshFrequency, Timeout.Infinite);
            };

            // Make sure the control properties get changed on the UI thread
            //
            if(Dispatcher.Thread != Thread.CurrentThread)
            {
                Dispatcher.Invoke(action);
            }
            else
            {
                action();
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  HandleDevStorageBug
        //
        // Parameters
        //  localVirtualMachine : The local virtual machine that is being
        //      backed up
        //
        // Returns
        //  The remote VM if an upload operation will not complete due to a bug
        //  in the dev storage, null otherwise
        //
        ///////////////////////////////////////////////////////////////////////
        private VirtualMachine HandleDevStorageBug(VirtualMachine localVirtualMachine)
        {
            if(IsDevStorage())
            {
                // Check to see if there is a remote VM with the same name
                //
                return VirtualMachineProvider.GetRemoteVirtualMachines(true).Where(vm => vm.Name == localVirtualMachine.Name).FirstOrDefault();
            }           

            return null;
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  IsDevStorage
        //
        // Returns
        //  True if the app is setup to run against dev storage, false if 
        //  using the live Azure storage endpoint.
        //
        ///////////////////////////////////////////////////////////////////////
        private bool IsDevStorage()
        {
            return ConfigurationManager.AppSettings["AccountName"] == "devstoreaccount1";
        }
	}
}